/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/module.h>
#include <linux/device.h>
#include <asm/unaligned.h>
#include <linux/mutex.h>
#include <linux/slab.h>

#include <alga/amd/atombios/atb.h>
#include <alga/amd/atombios/vm.h>

#include "interpreter.h"

#include "tables/atb.h"
#include "tables/cmd.h"
#include "tables/data.h"
#include "tables/volt_info.h"
#include "tables/i2c.h"
#include "tables/pp.h"

#include "atb.h"
#include "pp.h"

#define DONT_HAVE_X_CTL	0
#define HAVE_X_CTL	1
static long have_x_ctl(struct atombios *atb, u8 volt_type, u8 volt_mode)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	struct volt_info *info;
	long r;
	u16 tbl_sz;
	u8 *tbl_start;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.volt_info);
	info = atb->adev.rom + of;

	dev_info(atb->adev.dev, "atombios:volt_info (0x%04x) revision %u.%u\n",
			of, info->hdr.tbl_fmt_rev, info->hdr.tbl_content_rev);
	if (info->hdr.tbl_fmt_rev != 3 && info->hdr.tbl_content_rev != 1) {
		dev_err(atb->adev.dev, "atombios:volt_info revision not supported");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	/*
	 * We have vddc control, if there is a gpio to control vddc. We get this
	 * piece of information parsing the volt info, looking for the
	 * definition of a gpio control volt object for vddc.
	 */
	tbl_start = (u8*)info;
	tbl_sz = get_unaligned_le16(&info->hdr.sz);
	of = offsetof(struct volt_info, objs[0]);

	while (of < tbl_sz) {
		struct volt_info_obj_hdr *obj_hdr;

		obj_hdr = (struct volt_info_obj_hdr*)(tbl_start + of); 

		if (obj_hdr->type == volt_type
				&& obj_hdr->mode == volt_mode) {
			r = HAVE_X_CTL;
			goto unlock_mutex;
		}

		of += get_unaligned_le16(&obj_hdr->sz);
	}	

	r = DONT_HAVE_X_CTL;

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}

long atb_have_vddc_ctl(struct atombios *atb)
{
	long r;

	r = have_x_ctl(atb, VOLT_TYPE_VDDC, VOLT_OBJ_GPIO_LUT);
	switch (r) {
	case HAVE_X_CTL:
		r = ATB_HAVE_VDDC_CTL;
		break;
	case DONT_HAVE_X_CTL:
		r = ATB_DONT_HAVE_VDDC_CTL;
		break;
	}
	return r;
}
EXPORT_SYMBOL_GPL(atb_have_vddc_ctl);

long atb_have_mvddc_ctl(struct atombios *atb)
{
	long r;

	r = have_x_ctl(atb, VOLT_TYPE_MVDDC, VOLT_OBJ_GPIO_LUT);
	switch (r) {
	case HAVE_X_CTL:
		r = ATB_HAVE_MVDDC_CTL;
		break;
	case DONT_HAVE_X_CTL:
		r = ATB_DONT_HAVE_MVDDC_CTL;
		break;
	}
	return r;
}
EXPORT_SYMBOL_GPL(atb_have_mvddc_ctl);

long atb_have_vddci_ctl(struct atombios *atb)
{
	long r;

	r = have_x_ctl(atb, VOLT_TYPE_VDDCI, VOLT_OBJ_GPIO_LUT);
	switch (r) {
	case HAVE_X_CTL:
		r = ATB_HAVE_VDDCI_CTL;
		break;
	case DONT_HAVE_X_CTL:
		r = ATB_DONT_HAVE_VDDCI_CTL;
		break;
	}
	return r;
}
EXPORT_SYMBOL_GPL(atb_have_vddci_ctl);

long atb_have_vddc_phase_shed_ctl(struct atombios *atb)
{
	long r;

	r = have_x_ctl(atb, VOLT_TYPE_VDDC, VOLT_OBJ_GPIO_PHASE_SHED_LUT);
	switch (r) {
	case HAVE_X_CTL:
		r = ATB_HAVE_VDDC_PHASE_SHED_CTL;
		break;
	case DONT_HAVE_X_CTL:
		r = ATB_DONT_HAVE_VDDC_PHASE_SHED_CTL;
		break;
	}
	return r;
}
EXPORT_SYMBOL_GPL(atb_have_vddc_phase_shed_ctl);

static long volt_info_obj_gpio_parse(struct atombios *atb,
					struct volt_info_obj_gpio *obj_gpio,
						struct atb_volt_tbl *tbl)
{
	u8 i;

	if (obj_gpio->lut_entries_n > ATB_VOLT_TBL_ENTRIES_N_MAX) {
		dev_err(atb->adev.dev, "atombios:volt_info_obj_gpio has too many entries\n");
		return -ATB_ERR;
	}

	tbl->entries_n = obj_gpio->lut_entries_n;
	tbl->phase_delay = obj_gpio->phase_delay;
	tbl->mask_low = get_unaligned_le32(&obj_gpio->gpio_mask);

	for (i = 0; i < obj_gpio->lut_entries_n; ++i) {
		struct volt_info_obj_gpio_lut_entry *entry;

		entry = &obj_gpio->lut_entries[i];
		tbl->entries[i].val_mv = get_unaligned_le16(&entry->volt_val);
		tbl->entries[i].smio_low = get_unaligned_le32(&entry->volt_id);
	}
	return 0;
}

static long x_gpio_tbl_get(struct atombios *atb, struct atb_volt_tbl *tbl,
					u8 volt_type, u8 volt_mode)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	struct volt_info *info;
	long r;
	u16 tbl_sz;
	u8 *tbl_start;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.volt_info);
	info = atb->adev.rom + of;

	dev_info(atb->adev.dev, "atombios:volt_info (0x%04x) revision %u.%u\n",
			of, info->hdr.tbl_fmt_rev, info->hdr.tbl_content_rev);
	if (info->hdr.tbl_fmt_rev != 3 && info->hdr.tbl_content_rev != 1) {
		dev_err(atb->adev.dev, "atombios:volt_info revision not supported");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	/*
	 * We have vddc control, if there is a gpio to control vddc. We get this
	 * piece of information parsing the volt info, looking for the
	 * definition of a gpio control volt object for vddc.
	 */
	tbl_start = (u8*)info;
	tbl_sz = get_unaligned_le16(&info->hdr.sz);
	of = offsetof(struct volt_info, objs[0]);

	memset(tbl, 0, sizeof(*tbl));
	r = 0;

	while (of < tbl_sz) {
		struct volt_info_obj_hdr *obj_hdr;

		obj_hdr = (struct volt_info_obj_hdr*)(tbl_start + of); 

		if (obj_hdr->type == volt_type
					&& obj_hdr->mode == volt_mode) {
			struct volt_info_obj_gpio *obj_gpio;

			obj_gpio = (struct volt_info_obj_gpio*)obj_hdr;
			r = volt_info_obj_gpio_parse(atb, obj_gpio, tbl);
			goto unlock_mutex;
		}

		of += get_unaligned_le16(&obj_hdr->sz);
	}	

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}

long atb_vddc_tbl_get(struct atombios *atb, struct atb_volt_tbl *tbl)
{
	return x_gpio_tbl_get(atb, tbl, VOLT_TYPE_VDDC, VOLT_OBJ_GPIO_LUT);
}
EXPORT_SYMBOL_GPL(atb_vddc_tbl_get);

long atb_vddci_tbl_get(struct atombios *atb, struct atb_volt_tbl *tbl)
{
	return x_gpio_tbl_get(atb, tbl, VOLT_TYPE_VDDCI, VOLT_OBJ_GPIO_LUT);
}
EXPORT_SYMBOL_GPL(atb_vddci_tbl_get);

long atb_mvddc_tbl_get(struct atombios *atb, struct atb_volt_tbl *tbl)
{
	return x_gpio_tbl_get(atb, tbl, VOLT_TYPE_MVDDC, VOLT_OBJ_GPIO_LUT);
}
EXPORT_SYMBOL_GPL(atb_mvddc_tbl_get);

long atb_vddc_phase_shed_tbl_get(struct atombios *atb, struct atb_volt_tbl *tbl)
{
	return x_gpio_tbl_get(atb, tbl, VOLT_TYPE_VDDC,
						VOLT_OBJ_GPIO_PHASE_SHED_LUT);
}
EXPORT_SYMBOL_GPL(atb_vddc_phase_shed_tbl_get);

long volt_get(struct atombios *atb, u8 type, u16 lkge_idx, u16 *mv)
{
	u16 of;
	struct master_cmd_tbl *cmd_tbl;
	struct common_cmd_tbl_hdr *volt;
	struct volt_params *ps;
	long r;

	of = get_unaligned_le16(&atb->hdr->master_cmd_tbl_of);
	cmd_tbl = atb->adev.rom + of;
	of = get_unaligned_le16(&cmd_tbl->list.volt);

	volt = atb->adev.rom + of;
	dev_info(atb->adev.dev, "atombios:volt (0x%04x) revision %u.%u\n",
			of, volt->hdr.tbl_fmt_rev,volt->hdr.tbl_content_rev);
	if (volt->hdr.tbl_fmt_rev != 1 || volt->hdr.tbl_content_rev != 3) {
		dev_err(atb->adev.dev, "atombios:volt revision not supported");
		r = -ATB_ERR;
		goto exit;
	}

	atb->g_ctx.ps_dws = 0x80;/* max for ps index */
	atb->g_ctx.ps_top = kzalloc(atb->g_ctx.ps_dws * 4, GFP_KERNEL);
	if (!atb->g_ctx.ps_top) {
		dev_err(atb->adev.dev, "atombios:unable to allocate parameter space (stack)\n");
		r = -ATB_ERR;
		goto exit;
	}
	ps = (struct volt_params *)atb->g_ctx.ps_top;
	ps->type = type;
	ps->action = VOLT_ACTION_GET;
	put_unaligned_le16(lkge_idx, &ps->lvl);

	atb->g_ctx.fb_wnd = 0;
	atb->g_ctx.regs_blk = 0;
	atb->g_ctx.io_mode = IO_MM;

	r = interpret(atb, of, 0, 0);

	if (r == 0)
		*mv = get_unaligned_le16(&ps->lvl);

	kfree(atb->g_ctx.ps_top);

exit:
	return r;
}

long atb_volt_get(struct atombios *atb, u8 type, u16 lkge_idx, u16 *lvl)
{
	long r;

	mutex_lock(&atb->mutex);

	r = volt_get(atb, type, lkge_idx, lvl);

	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_volt_get);

long atb_volt_set(struct atombios *atb, u8 type, u16 lvl)
{
	u16 of;
	struct master_cmd_tbl *cmd_tbl;
	struct common_cmd_tbl_hdr *volt;
	struct volt_params *ps;
	long r;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_cmd_tbl_of);
	cmd_tbl = atb->adev.rom + of;
	of = get_unaligned_le16(&cmd_tbl->list.volt);

	volt = atb->adev.rom + of;
	dev_info(atb->adev.dev, "atombios:volt (0x%04x) revision %u.%u\n",
			of, volt->hdr.tbl_fmt_rev, volt->hdr.tbl_content_rev);
	if (volt->hdr.tbl_fmt_rev != 1 || volt->hdr.tbl_content_rev != 3) {
		dev_err(atb->adev.dev, "atombios:volt revision not supported");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	atb->g_ctx.ps_dws = 0x80;/* max for ps index */
	atb->g_ctx.ps_top = kzalloc(atb->g_ctx.ps_dws * 4, GFP_KERNEL);
	if (!atb->g_ctx.ps_top) {
		dev_err(atb->adev.dev, "atombios:unable to allocate parameter space (stack)\n");
		r = -ATB_ERR;
		goto unlock_mutex;
	}
	ps = (struct volt_params *)atb->g_ctx.ps_top;
	ps->type = type;
	ps->action = VOLT_ACTION_SET;
	put_unaligned_le16(lvl, &ps->lvl);

	atb->g_ctx.fb_wnd = 0;
	atb->g_ctx.regs_blk = 0;
	atb->g_ctx.io_mode = IO_MM;

	r = interpret(atb, of, 0, 0);
	kfree(atb->g_ctx.ps_top);

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_volt_set);

static long volt_on_clk_dep_tbl_parse(struct atombios *atb,
					struct atb_volt_on_clk_dep_tbl *dst,
						struct volt_on_clk_dep_tbl *src)
{
	u8 i;

	dst->entries_n = src->entries_n;
	dst->entries = kzalloc(sizeof(*(dst->entries)) * dst->entries_n,
								GFP_KERNEL);
	if (!dst->entries) {
		dev_err(atb->adev.dev, "atombios:unable to allocate memory for voltage on clk dependence table entries\n");
		return -ATB_ERR;
	}

	for (i = 0; i < dst->entries_n; ++i) {
		dst->entries[i].clk = 
				get_unaligned_le16(&src->entries[i].clk_low)
				| (src->entries[i].clk_hi << 16);
		dst->entries[i].volt_id = get_unaligned_le16(
						&src->entries[i].volt_id);
	}
	return 0;
}

long atb_vddc_dep_on_sclk_tbl_get(struct atombios *atb,
					struct atb_volt_on_clk_dep_tbl *tbl)
{
	u16 of;
	u16 pp_of;
	struct master_data_tbl *data_tbl;
	union pp *pp;
	long r;
	u16 tbl_sz;
	u16 vddc_dep_on_sclk_tbl_of;
	struct volt_on_clk_dep_tbl *src_dep_tbl;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	pp_of = get_unaligned_le16(&data_tbl->list.pp_info);
	pp = atb->adev.rom + pp_of;

	dev_info(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u\n",
		of, pp->pp0.hdr.tbl_fmt_rev, pp->pp0.hdr.tbl_content_rev);

	if (pp->pp0.hdr.tbl_fmt_rev != 6 && pp->pp0.hdr.tbl_content_rev != 1) {
		dev_err(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u not supported\n",
						pp_of, pp->pp0.hdr.tbl_fmt_rev,
						pp->pp0.hdr.tbl_content_rev);
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	tbl_sz = get_unaligned_le16(&pp->pp0.tbl_sz);
	if (tbl_sz < sizeof(struct pp3)) {
		dev_err(atb->adev.dev, "atombios:missing powerplay 3 table\n");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	r = 0;
	memset(tbl, 0, sizeof(*tbl));
	vddc_dep_on_sclk_tbl_of = get_unaligned_le16(
					&pp->pp3.vddc_dep_on_sclk_tbl_of);

	if (vddc_dep_on_sclk_tbl_of) {
		src_dep_tbl = atb->adev.rom + pp_of + vddc_dep_on_sclk_tbl_of;

		r = volt_on_clk_dep_tbl_parse(atb, tbl, src_dep_tbl);
	}

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_vddc_dep_on_sclk_tbl_get);

static long cac_lkge_tbl_parse(struct atombios *atb, struct cac_lkge_tbl *src,
						struct atb_cac_lkge_tbl *dst)
{
	u8 i;
	long r;
	u32 platform_caps;

	r = pp_platform_caps_get(atb, &platform_caps);
	if (r == -ATB_ERR)
		return -ATB_ERR;

	if (platform_caps & PP_PLATFORM_CAPS_EVV) {
		dev_err(atb->adev.dev, "atombios:unsupported cac leakage for evv platform\n");
		return -ATB_ERR;
	}

	dst->entries_n = src->entries_n;
	dst->entries = kzalloc(sizeof(*(dst->entries)) * dst->entries_n,
								GFP_KERNEL);
	if (!dst->entries) {
		dev_err(atb->adev.dev, "atombios:unable to allocate memory for cac leakage table entries\n");
		return -ATB_ERR;
	}

	for (i = 0; i < dst->entries_n; ++i) {
		dst->entries[i].vddc_mv = get_unaligned_le16(
						&src->entries[i].vddc_mv);
		dst->entries[i].lkge = get_unaligned_le32(
						&src->entries[i].lkge);
	}
	return 0;
}

/*
 * cac is related to pwrtune, not at all to the alternating current
 * capacitor, but pwr then watt (was told something like "calculation
 * accumulator".
 */ 
long atb_cac_lkge_tbl_get(struct atombios *atb, struct atb_cac_lkge_tbl *tbl)
{
	u16 of;
	u16 pp_of;
	struct master_data_tbl *data_tbl;
	union pp *pp;
	long r;
	u16 tbl_sz;
	u16 cac_lkge_tbl_of;
	struct cac_lkge_tbl *src_cac_tbl;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	pp_of = get_unaligned_le16(&data_tbl->list.pp_info);
	pp = atb->adev.rom + pp_of;

	dev_info(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u\n",
		of, pp->pp0.hdr.tbl_fmt_rev, pp->pp0.hdr.tbl_content_rev);

	if (pp->pp0.hdr.tbl_fmt_rev != 6 && pp->pp0.hdr.tbl_content_rev != 1) {
		dev_err(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u not supported\n",
						pp_of, pp->pp0.hdr.tbl_fmt_rev,
						pp->pp0.hdr.tbl_content_rev);
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	tbl_sz = get_unaligned_le16(&pp->pp0.tbl_sz);
	if (tbl_sz < sizeof(struct pp4)) {
		dev_err(atb->adev.dev, "atombios:missing powerplay 4 table\n");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	r = 0;
	memset(tbl, 0, sizeof(*tbl));
	cac_lkge_tbl_of = get_unaligned_le16(&pp->pp4.cac_lkge_tbl_of);

	if (cac_lkge_tbl_of) {
		src_cac_tbl = atb->adev.rom + pp_of + cac_lkge_tbl_of;

		r = cac_lkge_tbl_parse(atb, src_cac_tbl, tbl);	
	} 
unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_cac_lkge_tbl_get);

long atb_load_line_slope_get(struct atombios *atb, u16 *load_line_slope)
{
	u16 of;
	u16 pp_of;
	struct master_data_tbl *data_tbl;
	union pp *pp;
	long r;
	u16 tbl_sz;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	pp_of = get_unaligned_le16(&data_tbl->list.pp_info);
	pp = atb->adev.rom + pp_of;

	dev_info(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u\n",
		of, pp->pp0.hdr.tbl_fmt_rev, pp->pp0.hdr.tbl_content_rev);

	if (pp->pp0.hdr.tbl_fmt_rev != 6 && pp->pp0.hdr.tbl_content_rev != 1) {
		dev_err(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u not supported\n",
						pp_of, pp->pp0.hdr.tbl_fmt_rev,
						pp->pp0.hdr.tbl_content_rev);
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	tbl_sz = get_unaligned_le16(&pp->pp0.tbl_sz);
	if (tbl_sz < sizeof(struct pp4)) {
		dev_err(atb->adev.dev, "atombios:missing powerplay 4 table\n");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	*load_line_slope = get_unaligned_le16(&pp->pp4.load_line_slope);

	r = 0;

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_load_line_slope_get);

static long vddc_phase_shed_limits_tbl_parse(struct atombios *atb,
				struct vddc_phase_shed_limits_tbl *src,
				struct atb_vddc_phase_shed_limits_tbl *dst)
{
	u8 i;

	dst->entries_n = src->entries_n;
	dst->entries = kzalloc(sizeof(*(dst->entries)) * dst->entries_n,
								GFP_KERNEL);
	if (!dst->entries) {
		dev_err(atb->adev.dev, "atombios:unable to allocate memory for vddc phase shedding limits table entries\n");
		return -ATB_ERR;
	}

	for (i = 0; i < dst->entries_n; ++i) {
		dst->entries[i].vddc_mv = get_unaligned_le16(
						&src->entries[i].volt_mv);
		dst->entries[i].sclk =
			get_unaligned_le16(&src->entries[i].sclk_low)
			| (src->entries[i].sclk_hi << 16);
		dst->entries[i].mclk =
			get_unaligned_le16(&src->entries[i].mclk_low)
			| (src->entries[i].mclk_hi << 16);
	}
	return 0;
}

long atb_vddc_phase_shed_limits_tbl_get(struct atombios *atb,
				struct atb_vddc_phase_shed_limits_tbl *tbl)
{
	u16 of;
	u16 pp_of;
	struct master_data_tbl *data_tbl;
	union pp *pp;
	long r;
	u16 tbl_sz;
	u16 vddc_phase_shed_limits_tbl_of;
	struct vddc_phase_shed_limits_tbl *src_vddc_phase_shed_limits_tbl;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	pp_of = get_unaligned_le16(&data_tbl->list.pp_info);
	pp = atb->adev.rom + pp_of;

	dev_info(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u\n",
		of, pp->pp0.hdr.tbl_fmt_rev, pp->pp0.hdr.tbl_content_rev);

	if (pp->pp0.hdr.tbl_fmt_rev != 6 && pp->pp0.hdr.tbl_content_rev != 1) {
		dev_err(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u not supported\n",
						pp_of, pp->pp0.hdr.tbl_fmt_rev,
						pp->pp0.hdr.tbl_content_rev);
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	tbl_sz = get_unaligned_le16(&pp->pp0.tbl_sz);
	if (tbl_sz < sizeof(struct pp3)) {
		dev_err(atb->adev.dev, "atombios:missing powerplay 3 table\n");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	r = 0;
	memset(tbl, 0, sizeof(*tbl));
	vddc_phase_shed_limits_tbl_of =
		get_unaligned_le16(&pp->pp3.vddc_phase_shed_limits_tbl_of);

	if (vddc_phase_shed_limits_tbl_of) {
		src_vddc_phase_shed_limits_tbl = atb->adev.rom + pp_of
						+ vddc_phase_shed_limits_tbl_of;

		r = vddc_phase_shed_limits_tbl_parse(atb,
					src_vddc_phase_shed_limits_tbl, tbl);
	} 
unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_vddc_phase_shed_limits_tbl_get);

long atb_cac_lkge_get(struct atombios *atb, u32 *cac_lkge)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	union pp *pp;
	u16 tbl_sz;
	long r;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.pp_info);
	pp = atb->adev.rom + of;

	dev_info(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u\n",
		of, pp->pp0.hdr.tbl_fmt_rev, pp->pp0.hdr.tbl_content_rev);

	if (pp->pp0.hdr.tbl_fmt_rev != 6 && pp->pp0.hdr.tbl_content_rev != 1) {
		dev_err(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u not supported\n",
						of, pp->pp0.hdr.tbl_fmt_rev,
						pp->pp0.hdr.tbl_content_rev);
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	tbl_sz = get_unaligned_le16(&pp->pp0.tbl_sz);
	if (tbl_sz < sizeof(struct pp4)) {
		dev_err(atb->adev.dev, "atombios:missing powerplay 4 table\n");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	*cac_lkge = get_unaligned_le32(&pp->pp4.cac_lkge);

	r = 0;
	
unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_cac_lkge_get);

long atb_pp_volt_time_get(struct atombios *atb, u16 *volt_time)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	union pp *pp;
	long r;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.pp_info);
	pp = atb->adev.rom + of;

	dev_info(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u\n",
		of, pp->pp0.hdr.tbl_fmt_rev, pp->pp0.hdr.tbl_content_rev);

	if (pp->pp0.hdr.tbl_fmt_rev != 6 && pp->pp0.hdr.tbl_content_rev != 1) {
		dev_err(atb->adev.dev, "atombios:pp_info (0x%04x) revision %u.%u not supported\n",
						of, pp->pp0.hdr.tbl_fmt_rev,
						pp->pp0.hdr.tbl_content_rev);
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	*volt_time = get_unaligned_le16(&pp->pp0.volt_time);

	r = 0;
	
unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_pp_volt_time_get);
